/*
 * File Name：CWCrashHandler.java
 * Copyright：Copyright 2008-2015 CiWong.Inc. All Rights Reserved.
 * Description： CWCrashHandler.java
 * Modify By：res-jxliu
 * Modify Date：2015年3月21日
 * Modify Type：Add
 */
package com.ciwong.mobilelib.application;

import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.os.Build;
import android.os.Looper;
import android.text.format.Time;
import android.util.Log;

import com.ciwong.libs.utils.CWLog;
import com.ciwong.libs.utils.DateFormat;
import com.ciwong.libs.utils.FileUtils;

import java.io.File;
import java.io.FileOutputStream;
import java.io.FilenameFilter;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.io.Writer;
import java.lang.Thread.UncaughtExceptionHandler;
import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.Properties;
import java.util.TreeSet;

/**
 * 全局捕获异常类
 * 
 * @author res-jxliu
 * @version ciwong v.1.0 2015年3月21日
 * @since ciwong v.1.0
 */
public class CWCrashHandler implements UncaughtExceptionHandler
{

    /** Debug Log tag */
    public static final String TAG = "CWCrashHandler";

    /**
     * NEW_LINE
     */
    public static final String NEW_LINE = System.getProperty("line.separator");

    /**
     * 是否开启日志输出,在Debug状态下开启, 在Release状态下关闭以提示程序性能
     * */
    public static final boolean DEBUG = CWLog.isDebug();

    private static final String VERSION_NAME = "versionName";

    private static final String VERSION_CODE = "versionCode";

    private static final String STACK_TRACE = "STACK_TRACE";

    /** CWCrashHandler实例 */
    private static CWCrashHandler instance;

    /** 错误报告文件的扩展名 */
    private static final String CRASH_REPORTER_EXTENSION = ".cr";

    /** 系统默认的UncaughtException处理类 */
    private UncaughtExceptionHandler mDefaultHandler;

    /** 程序的Context对象 */
    private BaseApplication mApplication;

    /** 使用Properties来保存设备的信息和错误堆栈信息 */
    private Properties mDeviceCrashInfo = new Properties();

    private  CrashReportListener mCrashReportListener;

    /** 保证只有一个CrashHandler实例 */
    private CWCrashHandler()
    {
    }

    /**
     * 获取CrashHandler实例 ,单例模式
     * 
     * @return CWCrashHandler实例
     * */
    public static CWCrashHandler getInstance()
    {
        if (instance == null)
        {
            instance = new CWCrashHandler();
        }
        return instance;
    }

    /**
     * 初始化,注册Context对象, 获取系统默认的UncaughtException处理器, 设置该CrashHandler为程序的默认处理器
     * 
     * @param application
     */
    public void init(BaseApplication application)
    {
        mApplication = application;
        mDefaultHandler = Thread.getDefaultUncaughtExceptionHandler();
        Thread.setDefaultUncaughtExceptionHandler(this);
    }

    /**
     * 当UncaughtException发生时会转入该函数来处理 {@inheritDoc}
     */
    @Override
    public void uncaughtException(Thread thread, Throwable ex)
    {
        if (!handleException(ex) && mDefaultHandler != null)
        {
            // 如果用户没有处理则让系统默认的异常处理器来处理
            mDefaultHandler.uncaughtException(thread, ex);
        }
        else
        {
            // Sleep一会后结束程序
            try
            {
                final int time = 3000;
                Thread.sleep(time);
            }
            catch (InterruptedException e)
            {
                Log.e(TAG, "Error : ", e);
            }
            mApplication.exit(true);
        }
    }

    /**
     * 自定义错误处理,收集错误信息 发送错误报告等操作均在此完成. 开发者可以根据自己的情况来自定义异常处理逻辑
     * 
     * @param ex
     * @return true:如果处理了该异常信息;否则返回false
     */
    private boolean handleException(final Throwable ex)
    {
        if (ex == null)
        {
            Log.w(TAG, "handleException --- ex==null");
            return true;
        }
        new Thread()
        {
            @Override
            public void run()
            {
                Looper.prepare();
//                CWToast.error(mApplication,
//                        CWResource.getStringId("app_error_tips")).show();
                Looper.loop();
            }
        }.start();
        ex.printStackTrace();
        // 收集设备信息
        collectCrashDeviceInfo(mApplication);
        // 保存错误报告文件
        saveCrashInfoToFile(ex);

//        CWLog.eOut("YZS_LOG",BaseApplication.log.toString());

        // mApplication.doExit(true);
        // 发送错误报告到服务器
        // sendCrashReportsToServer(mContext);
        return true;
    }

    /**
     * 在程序启动时候, 可以调用该函数来发送以前没有发送的报告
     */
    public void sendPreviousReportsToServer()
    {
        sendCrashReportsToServer(mApplication);
    }

    /**
     * 把错误报告发送给服务器,包含新产生的和以前没发送的.
     * 
     * @param ctx
     */
    private void sendCrashReportsToServer(BaseApplication ctx)
    {
        String[] crFiles = getCrashReportFiles(ctx);
        if (crFiles != null && crFiles.length > 0)
        {
            TreeSet<String> sortedFiles = new TreeSet<String>();
            sortedFiles.addAll(Arrays.asList(crFiles));
            for (String fileName : sortedFiles)
            {
                File cr = new File(ctx.getFilesDir(), fileName);
                postReport(cr);
                cr.delete();// 删除已发送的报告
            }
        }
    }

    private void postReport(File file)
    {

    }

    /**
     * 获取错误报告文件名
     * 
     * @param ctx
     * @return
     */
    private String[] getCrashReportFiles(BaseApplication ctx)
    {
        File filesDir = ctx.getFilesDir();
        FilenameFilter filter = new FilenameFilter()
        {
            public boolean accept(File dir, String name)
            {
                return name.endsWith(CRASH_REPORTER_EXTENSION);
            }
        };
        return filesDir.list(filter);
    }

    /**
     * 保存错误信息到文件中
     * 
     * @param ex
     * @return
     */
    private String saveCrashInfoToFile(Throwable ex)
    {
        Writer info = new StringWriter();
        PrintWriter printWriter = new PrintWriter(info);
        ex.printStackTrace(printWriter);
        Throwable cause = ex.getCause();
        while (cause != null)
        {
            cause.printStackTrace(printWriter);
            cause = cause.getCause();
        }
        String result = info.toString();
        printWriter.close();
        // mDeviceCrashInfo.put("EXEPTION", ex.getLocalizedMessage());
        Log.d(TAG, "########CWCrashHandler result######"+result);

        if (mCrashReportListener != null) {
            mCrashReportListener.onCrashReport(result);
        }

        mDeviceCrashInfo.put(STACK_TRACE, result);
        try
        {
            Time t = new Time("GMT+8");
            t.setToNow(); // 取得系统时间
            String fileName = "crash-" + t.year + "-" + (t.month + 1) + "-"
                    + t.monthDay + CRASH_REPORTER_EXTENSION;
            File f = new File(FileUtils.getLogPath() + "/log");
            if (!f.exists())
            {
                f.mkdirs();
            }
            if (f.exists())
            {
                FileOutputStream trace = new FileOutputStream(new File(f,
                        fileName), true);
                mDeviceCrashInfo.store(
                        trace,
                        "-----------------"
                                + DateFormat.formatDate(System
                                        .currentTimeMillis())
                                + "------------------");
                trace.flush();
                trace.close();
            }
            return fileName;
        }
        catch (Exception e)
        {
            Log.e(TAG, "an error occured while writing report file...", e);
        }
        return null;
    }

    /**
     * 收集程序崩溃的设备信息
     * 
     * @param ctx
     *            上下文
     */
    public void collectCrashDeviceInfo(BaseApplication ctx)
    {
        try
        {
            PackageManager pm = ctx.getPackageManager();
            PackageInfo pi = pm.getPackageInfo(ctx.getPackageName(),
                    PackageManager.GET_ACTIVITIES);
            if (pi != null)
            {
                mDeviceCrashInfo.put(VERSION_NAME,
                        pi.versionName == null ? "not set" : pi.versionName);
                mDeviceCrashInfo.put(VERSION_CODE, "" + pi.versionCode);
            }
        }
        catch (NameNotFoundException e)
        {
            Log.e(TAG, "Error while collect package info", e);
        }
        // 使用反射来收集设备信息.在Build类中包含各种设备信息,
        // 例如: 系统版本号,设备生产商 等帮助调试程序的有用信息
        // 具体信息请参考后面的截图
        Field[] fields = Build.class.getDeclaredFields();
        for (Field field : fields)
        {
            try
            {
                field.setAccessible(true);
                mDeviceCrashInfo.put(field.getName(), "" + field.get(null));
                if (DEBUG)
                {
                    Log.d(TAG, field.getName() + " : " + field.get(null));
                }
            }
            catch (Exception e)
            {
                Log.e(TAG, "Error while collect crash info", e);
            }
        }
    }


    public void setCrashReportListener(CrashReportListener listener)
    {
        mCrashReportListener=listener;
    }


    public  interface  CrashReportListener{
        void onCrashReport(String crashInfo);
    }

}
